package controllers

import (
	"covalence/models"
	"covalence/util"
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	"os"
	"strconv"
	"strings"
	//"time"
)

type LigandController struct {
	baseController
}

func (this *LigandController) Index() {
	this.TplNames = "index.html"
}

func (this *LigandController) ShowHTML() {
	this.TplNames = "MoleculeBrowse.html"
}

func (this *LigandController) ShowResultsHTML() {
	this.TplNames = "MolSearchResults.html"
}

/*
** Ligands Browser for one of multiple pages
** Display basic informaiton of ligand
 */
func (this *LigandController) Browse() {

	fmt.Println("Ligand browse")
	/*
	**	get limited ligands info from ligand table
	 */
	start, _ := this.GetInt64("startMol")
	limit, _ := this.GetInt64("perPageMolCount")
	fmt.Println(start)
	fmt.Println(limit)
	if this.dbBasicInfo.LigandsCount < (start + limit) {
		limit = this.dbBasicInfo.LigandsCount - start
	}

	var ligands []models.Molecule
	qs := orm.NewOrm().QueryTable("molecule")
	_, err := qs.Limit(limit, start).All(&ligands)
	if err != nil {
		this.responseMsg.ErrorMsg("", nil)
		this.Data["json"] = this.responseMsg
		this.ServeJson()
		return
	}

	/*
	**	get basic info of ligands
	**  step 1: get data from relation table
	**  step 2: get TrgName from target table
	 */
	var basicLigands []models.LigandBasicInfo
	for _, ligand := range ligands {
		var basicLig models.LigandBasicInfo
		basicLig.MolId = ligand.MolId
		basicLig.MolName = ligand.MolName
		basicLig.Cas = ligand.Cas
		basicLig.MolImg = ligand.MolImg
		basicLig.MolFile = ligand.MolFile

		var relations []models.Relation
		qs := orm.NewOrm().QueryTable("relation")
		_, err := qs.Filter("MolId", ligand.MolId).All(&relations)
		if err != nil || len(relations) == 0 {
			basicLig.Warhead = ""
			basicLig.Mechanism = ""
			basicLigands = append(basicLigands, basicLig)
			continue
		}

		basicLig.Warhead = relations[0].Warhead
		basicLig.Mechanism = relations[0].Mechanism
		for _, rel := range relations {
			var trg models.Target
			trg.TrgId = rel.TrgId
			err := trg.Read("TrgId")
			if err != nil {
				continue
			}
			basicLig.TrgName = append(basicLig.TrgName, trg.TrgName)
		}
		basicLigands = append(basicLigands, basicLig)
	}

	/*
	** send basic data of ligand back
	 */
	data := make(map[string]interface{})
	data["totalLigandsCount"] = this.dbBasicInfo.LigandsCount
	data["ligands"] = basicLigands

	this.responseMsg.SuccessMsg("", data)
	this.Data["json"] = this.responseMsg
	this.ServeJson()
	//this.TplNames = "chemdb_browse.html"
}

/*
** Display details informaiton of one ligand
 */
func (this *LigandController) GetDetailsOfLigand() {

	fmt.Println("Ligand details")

	molID := this.GetString("molId")
	fmt.Println(molID)

	var detailsInfo models.LigandDetailsInfo
	detailsInfo.Ligand.MolId = molID
	err := detailsInfo.Ligand.Read("MolId")
	if err != nil {
		this.responseMsg.ErrorMsg("", nil)
		this.Data["json"] = this.responseMsg
		this.ServeJson()
		return
	}

	/*
	**	get details info of ligand
	**  step 1: get data from relation table
	**  step 2: get data from target table
	**  step 3: get data from reference table
	 */
	qs := orm.NewOrm().QueryTable("relation")
	_, err = qs.Filter("MolId", molID).All(&detailsInfo.Relations)

	for _, rel := range detailsInfo.Relations {
		var target models.Target
		target.TrgId = rel.TrgId
		target.Read("TrgId")
		detailsInfo.Targets = append(detailsInfo.Targets, &target)
	}

	qs = orm.NewOrm().QueryTable("reference")
	_, err = qs.Filter("MolId", molID).All(&detailsInfo.References)

	qs = orm.NewOrm().QueryTable("indication")
	_, err = qs.Filter("MolId", molID).All(&detailsInfo.Indications)

	/*
	** send basic data of ligand back
	 */
	data := make(map[string]interface{})
	data["ligandDetails"] = detailsInfo

	this.responseMsg.SuccessMsg("", data)
	this.Data["json"] = this.responseMsg
	this.ServeJson()
}

/*
** three types of structure search: exact、sub and sim
 */
func (this *LigandController) StructureQuery() {
	fmt.Println("Structure query")

	searchType := strings.TrimSpace(this.GetString("searchType"))
	mol := this.GetString("mol")

	/*
	** save molfile to folder: userTmpFilePath/currentTime(unixtime )/
	 */
	folder := fmt.Sprintf("%s/%d", this.UserTmpFilePath, this.getTime().UnixNano())
	err := os.MkdirAll(folder, os.ModeDir|os.ModePerm)
	if err != nil {
		this.responseMsg.ErrorMsg("", nil)
		this.Data["json"] = this.responseMsg
		this.ServeJson()
		//panic(err)
		return
	}
	molName := "tmp"
	molFile := folder + "/" + molName + ".mol"
	fo, _ := os.Create(molFile)
	defer fo.Close()
	_, err = fo.WriteString(mol)
	if err != nil {
		this.responseMsg.ErrorMsg("", nil)
		this.Data["json"] = this.responseMsg
		this.ServeJson()
		//panic(err)
		return
	}

	/*
	** run the query
	** record query job information
	 */
	var queryJob models.QueryJob
	queryJob.QueryId = fmt.Sprintf("%d", this.getTime().UnixNano())
	queryJob.SubmitTime = this.getTime()
	queryJob.Parameters = mol
	/*
	 ** run query search
	 */
	if searchType == "exact" {
		queryJob.Type = "exact"
		structure := util.ExactSearch(folder, molName)
		if structure != nil {
			queryJob.NumHits = 1
			queryJob.Result = structure.MolId
			queryJob.FinishTime = this.getTime()
			queryJob.Insert()

			data := make(map[string]interface{})
			data["queryJobId"] = queryJob.QueryId
			this.responseMsg.SuccessMsg("", data)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		} else {
			this.responseMsg.ErrorMsg("There is no such compound", nil)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		}

	} else if searchType == "sub" {
		//numPerPage, _ := this.GetInt("perPageMolCount")
		queryJob.Type = "sub"

		subSearchEXE := beego.AppConfig.String("subSearchEXE")

		var hitsTXT string
		hitsTXT, err = util.SubSearch(subSearchEXE, this.dbBasicInfo.SDFile, molFile, folder)
		if err != nil || hitsTXT == "" {
			this.responseMsg.ErrorMsg("", nil)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
			return
		}
		hits := util.ReadSubSearchRes(hitsTXT)
		if len(hits) > 0 {
			var queryRes string
			for _, v := range hits {
				queryRes += v + ";"
			}
			queryRes = util.SubString(queryRes, 0, len(queryRes)-1)
			queryJob.Result = queryRes
			queryJob.NumHits = int64(len(hits))
			queryJob.FinishTime = this.getTime()
			queryJob.Insert()

			data := make(map[string]interface{})
			data["queryJobId"] = queryJob.QueryId

			/*if numPerPage > len(hits) {
				numPerPage = len(hits)
			}
			var mols []*models.Molecule
			for i := 0; i < numPerPage; i++ {
				var mol models.Molecule
				mol.MolId = hits[i]
				err = mol.Read("MolId")
				if err == nil {
					mols = append(mols, &mol)
				}

			}*/

			//data["mols"] = mols
			this.responseMsg.SuccessMsg("", data)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		} else {
			this.responseMsg.ErrorMsg("There is no results", nil)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		}
	} else if searchType == "sim" {
		//numPerPage, _ := this.GetInt("perPageMolCount")
		threshold := this.GetString("threshold")
		queryJob.Type = "sim"

		simSearchEXE := beego.AppConfig.String("simSearchEXE")

		var hitsTXT string
		hitsTXT, err = util.SimSearch(simSearchEXE, this.dbBasicInfo.SDFile, molFile, folder, "1000", threshold)
		if err != nil || hitsTXT == "" {
			this.responseMsg.ErrorMsg("", nil)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
			return
		}
		hits := util.ReadSimHits(hitsTXT)
		if len(hits) > 0 {
			queryRes, _ := json.Marshal(hits)
			queryJob.Result = string(queryRes)
			queryJob.NumHits = int64(len(hits))
			queryJob.FinishTime = this.getTime()
			queryJob.Insert()

			data := make(map[string]interface{})
			//data["queryRes"] = queryRes
			data["queryJobId"] = queryJob.QueryId

			/*if numPerPage > len(hits) {
				numPerPage = len(hits)
			}
			var mols []*models.Molecule
			var simValues []float64
			for i := 0; i < numPerPage; i++ {
				var mol models.Molecule
				mol.MolId = hits[i].MolName
				err = mol.Read("MolId")

				if err == nil {
					mols = append(mols, &mol)
					simValues = append(simValues, hits[i].SimValue)
				}
			}*/

			//data["mols"] = mols
			//data["simi"] = simValues
			this.responseMsg.SuccessMsg("", data)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		} else {
			this.responseMsg.ErrorMsg("There is no results", nil)
			this.Data["json"] = this.responseMsg
			this.ServeJson()
		}
	}
}

/*
** SMILES,InChIKey,CAS,PubChem,molName,ChEMBL
 */
func (this *LigandController) TextQuery() {
	fmt.Println("Text query")

	searchType := strings.TrimSpace(this.GetString("searchType"))
	//numPerPage, _ := this.GetInt64("perPageMolCount")
	queryString := this.GetString("queryString")

	var queryJob models.QueryJob
	queryJob.QueryId = fmt.Sprintf("%d", this.getTime().UnixNano())
	queryJob.SubmitTime = this.getTime()
	queryJob.Parameters = queryString

	var mols []*models.Molecule
	switch searchType {
	case "SMILES":
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			fmt.Println("smile_search: " + queryString)
			for _, smi := range queryArray {
				var mol models.Molecule
				mol.Smile = smi
				err := mol.Read("Smile")
				if err == nil {
					mols = append(mols, &mol)
				}
			}
		}
	case "InChIKey":
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			for _, inchi := range queryArray {
				var mol models.Molecule
				mol.InchiKey = inchi
				err := mol.Read("InchiKey")
				if err == nil {
					mols = append(mols, &mol)
				}
			}
		}
	case "CAS":
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			for _, cas := range queryArray {
				var mol models.Molecule
				mol.Cas = cas
				err := mol.Read("Cas")
				if err == nil {
					mols = append(mols, &mol)
				}
			}
		}
	case "PubChemCID":
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			for _, pub := range queryArray {
				var mol models.Molecule
				mol.Pubchem = pub
				err := mol.Read("Pubchem")
				if err == nil {
					mols = append(mols, &mol)
				}
			}
		}
	case "ChEMBL":
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			for _, chembl := range queryArray {
				var mol models.Molecule
				mol.Chembl = chembl
				err := mol.Read("Chembl")
				if err == nil {
					mols = append(mols, &mol)
				}
			}
		}
	case "mol_weight": // the range from min to max
		{
			queryArray := strings.Split(strings.TrimSpace(queryString), ";")
			if len(queryArray) == 2 {
				min, _ := strconv.ParseFloat(strings.TrimSpace(queryArray[0]), 64)
				max, _ := strconv.ParseFloat(strings.TrimSpace(queryArray[1]), 64)
				qs := orm.NewOrm().QueryTable("molecule")
				qs.Filter("MolWeight__gte", min).Filter("MolWeight__lte", max).All(&mols)
			}

		}
	case "name_search": // %like%
		{
			qs := orm.NewOrm().QueryTable("molecule")
			qs.Filter("MolName__icontains", strings.TrimSpace(queryString)).All(&mols)
		}
	}

	/*
	** save queryJob and return queryJob Id
	 */
	var hitMolIds string
	for _, mol := range mols {
		hitMolIds += mol.MolId + ";"
	}
	hitMolIds = util.SubString(hitMolIds, 0, len(hitMolIds)-1)
	queryJob.FinishTime = this.getTime()
	queryJob.NumHits = int64(len(mols))
	queryJob.Result = hitMolIds
	queryJob.Insert()

	/*if numPerPage > queryJob.NumHits {
		numPerPage = queryJob.NumHits
	}*/

	data := make(map[string]interface{})
	data["queryJobId"] = queryJob.QueryId
	//data["numHits"] = queryJob.NumHits
	//data["ligands"] = mols[:numPerPage]
	this.responseMsg.SuccessMsg("", data)
	this.Data["json"] = this.responseMsg
	this.ServeJson()
}

/*
** search by target or gene
** target: targetName, uniprotkb
** gene: hgncId, geneName
 */
func (this *LigandController) QueryByTrgOrGene() {
	fmt.Println("Text query")

	searchType := strings.TrimSpace(this.GetString("searchType"))
	//numPerPage, _ := this.GetInt64("perPageMolCount")
	queryString := strings.TrimSpace(this.GetString("queryString"))
	var queryJob models.QueryJob
	queryJob.QueryId = fmt.Sprintf("%d", this.getTime().UnixNano())
	queryJob.SubmitTime = this.getTime()
	queryJob.Parameters = queryString

	/*
	** find the target by targetName/uniprotkb/hgncId/geneName
	 */
	var targets []*models.Target
	var err error
	switch searchType {
	case "targetName":
		{
			//target.TrgName = queryString
			qs := orm.NewOrm().QueryTable("target")
			_, err = qs.Filter("TrgName__iexact", queryString).All(&targets)
			//err = target.Read("TrgName")
		}
	case "uniprotkb":
		{
			qs := orm.NewOrm().QueryTable("target")
			_, err = qs.Filter("UniprotKb", queryString).All(&targets)
			//target.UniprotKb = queryString
			//err = target.Read("UniprotKb")
		}
	case "hgncId":
		{
			qs := orm.NewOrm().QueryTable("target")
			_, err = qs.Filter("HgncId", queryString).All(&targets)
			//target.HgncId = queryString
			//err = target.Read("HgncId")
		}
	case "geneName":
		{
			var cnt int64
			qs := orm.NewOrm().QueryTable("target")
			cnt, err = qs.Filter("Gene", queryString).All(&targets)
			if cnt == 0 {
				_, err = qs.Filter("GeneSynonyms", queryString).All(&targets)
			}
		}
	}
	if err != nil {
		this.responseMsg.ErrorMsg("", nil)
		this.Data["json"] = this.responseMsg
		this.ServeJson()
		return
	}

	var mols []*models.Molecule
	var molIds []string
	for _, target := range targets {
		var rels []*models.Relation
		qs := orm.NewOrm().QueryTable("relation")
		qs.Filter("TrgId", target.TrgId).All(&rels)
		for _, rel := range rels {
			var mol models.Molecule
			mol.MolId = rel.MolId
			err := mol.Read("MolId")
			if err == nil && !util.InArrayString(molIds, mol.MolId) {
				mols = append(mols, &mol)
				molIds = append(molIds, mol.MolId)
			}
		}
	}

	/*
	** save queryJob and return queryJob Id
	 */
	var hitMolIds string
	for _, mol := range mols {
		hitMolIds += mol.MolId + ";"
	}
	hitMolIds = util.SubString(hitMolIds, 0, len(hitMolIds)-1)
	queryJob.FinishTime = this.getTime()
	queryJob.NumHits = int64(len(mols))
	queryJob.Result = hitMolIds
	queryJob.Insert()

	/*if numPerPage > queryJob.NumHits {
		numPerPage = queryJob.NumHits
	}*/

	data := make(map[string]interface{})
	data["queryJobId"] = queryJob.QueryId
	//data["numHits"] = queryJob.NumHits
	//data["ligands"] = mols[:numPerPage]
	this.responseMsg.SuccessMsg("", data)
	this.Data["json"] = this.responseMsg
	this.ServeJson()
}

func (this *LigandController) GetQueryHits() {
	queryJobId := strings.TrimSpace(this.GetString("queryJobId"))
	start, _ := this.GetInt64("startMol")
	numPerPage, _ := this.GetInt64("perPageMolCount")

	var queryJob models.QueryJob
	queryJob.QueryId = queryJobId
	queryJob.Read("QueryId")
	end := start + numPerPage
	if end > queryJob.NumHits {
		end = queryJob.NumHits
	}

	var hitsMolIds []string

	if queryJob.Type == "sim" {
		var hitList []*util.SimSearchRes
		json.Unmarshal([]byte(queryJob.Result), &hitList)
		for _, hit := range hitList {
			hitsMolIds = append(hitsMolIds, hit.MolName)
		}
	} else {
		hitsMolIds = strings.Split(queryJob.Result, ";")
	}
	fmt.Println(start)
	fmt.Println(numPerPage)
	var mols []*models.Molecule
	for i := start; i < end; i++ {
		var mol models.Molecule
		mol.MolId = hitsMolIds[i]
		err := mol.Read("MolId")
		if err == nil {
			mols = append(mols, &mol)
		}
	}

	/*
	**	get basic info of ligands
	**  step 1: get data from relation table
	**  step 2: get TrgName from target table
	 */
	var basicLigands []models.LigandBasicInfo
	for _, ligand := range mols {
		var basicLig models.LigandBasicInfo
		basicLig.MolId = ligand.MolId
		basicLig.MolName = ligand.MolName
		basicLig.Cas = ligand.Cas
		basicLig.MolImg = ligand.MolImg
		basicLig.MolFile = ligand.MolFile

		var relations []models.Relation
		qs := orm.NewOrm().QueryTable("relation")
		_, err := qs.Filter("MolId", ligand.MolId).All(&relations)
		if err != nil || len(relations) == 0 {
			basicLig.Warhead = ""
			basicLig.Mechanism = ""
			basicLigands = append(basicLigands, basicLig)
			continue
		}

		basicLig.Warhead = relations[0].Warhead
		basicLig.Mechanism = relations[0].Mechanism
		for _, rel := range relations {
			var trg models.Target
			trg.TrgId = rel.TrgId
			err := trg.Read("TrgId")
			if err != nil {
				continue
			}
			basicLig.TrgName = append(basicLig.TrgName, trg.TrgName)
		}
		basicLigands = append(basicLigands, basicLig)
	}

	/*
	** send basic data of ligand back
	 */

	data := make(map[string]interface{})
	data["queryJobId"] = queryJob.QueryId
	data["totalLigandsCount"] = queryJob.NumHits
	data["ligands"] = basicLigands
	this.responseMsg.SuccessMsg("", data)
	this.Data["json"] = this.responseMsg
	this.ServeJson()
}

/*
** for download example
 */
func (this *LigandController) Marvin4js() {
	this.Ctx.Output.Download("marvin4js-license.cxl")
}
